package net.siufung.security;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.siufung.core.result.Result;
import net.siufung.core.result.ResultFail;
import net.siufung.core.utils.GsonUtil;
import net.siufung.core.utils.StringPool;
import net.siufung.security.api.properties.SecurityProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.authentication.ProviderNotFoundException;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.util.AntPathMatcher;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * 资源服务器
 * @author 陈建峰
 * @since 2022/4/30 10:29 下午
 */
@SuppressWarnings("deprecation")
@Slf4j
@Configuration
@EnableResourceServer
@EnableConfigurationProperties({SecurityProperties.class})
@AllArgsConstructor
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    private final TokenStore tokenStore;
    private final SecurityProperties securityProperties;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>
                .ExpressionInterceptUrlRegistry config = http.requestMatchers().anyRequest()
                .and().authorizeRequests();
        List<String> ignoreUrls = securityProperties.getIgnore().getUrls();
        ignoreUrls.forEach(e -> {
            config.antMatchers(e).permitAll();
        });
        config.anyRequest().authenticated();
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resource) throws Exception {
        resource
                .tokenStore(tokenStore)
                .authenticationEntryPoint(new AuthenticationEntryPoint())
                .tokenExtractor(new BearerTokenExtractor(securityProperties.getIgnore().getUrls()))
                .accessDeniedHandler(new AccessDeniedHandler());
    }

    /**
     * 拒绝访问处理程序
     * @author 陈建峰
     * @since 2022/4/30 10:29 下午
     */
    static
    class AccessDeniedHandler implements org.springframework.security.web.access.AccessDeniedHandler {

        @Override
        public void handle(HttpServletRequest request, HttpServletResponse response,
                           AccessDeniedException accessDeniedException) throws IOException, ServletException {
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            response.setCharacterEncoding(StringPool.UTF_8);
            Result<String> result = Result.of(false, ResultFail.AUTH_ACCESS_DENIED);
            result.setUrl(request.getRequestURI());
            response.getWriter().write(GsonUtil.toGsonString(result));
        }
    }

    /**
     * 请求头控制
     * @author 陈建峰
     * @since 2022/3/3 4:03 下午
     */
    @SuppressWarnings("deprecation")
    @RequiredArgsConstructor
    static
    class BearerTokenExtractor extends org.springframework.security.oauth2.provider.authentication.BearerTokenExtractor {

        private final List<String> ignoreUrls;

        @Override
        public Authentication extract(HttpServletRequest request) {
            boolean match = ignoreUrls.stream()
                    .anyMatch(url -> new AntPathMatcher().match(url, request.getRequestURI()));
            return match ? null : super.extract(request);
        }
    }

    /**
     * @author 陈建峰
     * @since 2022/3/3 4:03 下午
     */
    @Slf4j
    @NoArgsConstructor
    static
    class AuthenticationEntryPoint implements org.springframework.security.web.AuthenticationEntryPoint {

        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response,
                             AuthenticationException authException) {
            response.setCharacterEncoding(StringPool.UTF_8);
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            Result<String> result = new Result<>();
            if (authException instanceof InsufficientAuthenticationException) {
                result = Result.fail(ResultFail.AUTH_INSUFFICIENT_SCOPE);
            }
            if (authException instanceof ProviderNotFoundException) {
                result = Result.fail(ResultFail.AUTH_INVALID_GRANT);
            }
            result.setUrl(request.getServletPath());
            result.setData(null);
            PrintWriter writer = null;
            try {
                response.setStatus(HttpStatus.UNAUTHORIZED.value());
                response.setStatus(HttpStatus.OK.value());
                response.setCharacterEncoding(StandardCharsets.UTF_8.name());
                writer = response.getWriter();
                writer.println(GsonUtil.toGsonString(result));
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if(writer != null){
                    writer.close();
                }
            }
        }
    }
}
